using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;

using Pegasus.Diagnostics;

namespace Pegasus
{
	/// <summary>
	/// 
	/// </summary>
	[ Serializable ]
	public class DateTimeReoccurringWeekly : DateTimeReoccurring
	{
		// Local Instance Values
		private int m_numberOfWeeks = 1;
		private bool[] m_validDay = new bool[ 7 ];

		/// <summary>
		/// Initializes a new instance of the <see cref="T:DateTimeReoccurringWeekly"/> class.
		/// </summary>
		public DateTimeReoccurringWeekly()
		{
			// Initialize the array;
			m_validDay[ 0 ] = true;
			for( int x = 1; x < 7; x++ )
			{
				m_validDay[ x ] = false;
			}
		}

		/// <summary>
		/// Get/Set the number of days between occurances (1 to 52).
		/// </summary>
		public int EveryNumberOfWeeks
		{
			get
			{
				return m_numberOfWeeks;
			}

			set
			{
				ParamCode.Assert( value > 0 && value < 53, "EveryNumberOfWeeks" );
				m_numberOfWeeks = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="T:DateTimeReoccurringWeekly"/> is sunday.
		/// </summary>
		/// <value><c>true</c> if sunday; otherwise, <c>false</c>.</value>
		public bool Sunday
		{
			get
			{
				return m_validDay[ (int) DayOfWeek.Sunday ];
			}

			set
			{
				m_validDay[ (int) DayOfWeek.Sunday ] = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="T:DateTimeReoccurringWeekly"/> is monday.
		/// </summary>
		/// <value><c>true</c> if monday; otherwise, <c>false</c>.</value>
		public bool Monday
		{
			get
			{
				return m_validDay[ (int) DayOfWeek.Monday ];
			}

			set
			{
				m_validDay[ (int) DayOfWeek.Monday ] = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="T:DateTimeReoccurringWeekly"/> is tuesday.
		/// </summary>
		/// <value><c>true</c> if tuesday; otherwise, <c>false</c>.</value>
		public bool Tuesday
		{
			get
			{
				return m_validDay[ (int) DayOfWeek.Tuesday ];
			}

			set
			{
				m_validDay[ (int) DayOfWeek.Tuesday ] = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="T:DateTimeReoccurringWeekly"/> is wednesday.
		/// </summary>
		/// <value><c>true</c> if wednesday; otherwise, <c>false</c>.</value>
		public bool Wednesday
		{
			get
			{
				return m_validDay[ (int) DayOfWeek.Wednesday ];
			}

			set
			{
				m_validDay[ (int) DayOfWeek.Wednesday ] = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="T:DateTimeReoccurringWeekly"/> is thursday.
		/// </summary>
		/// <value><c>true</c> if thursday; otherwise, <c>false</c>.</value>
		public bool Thursday
		{
			get
			{
				return m_validDay[ (int) DayOfWeek.Thursday ];
			}

			set
			{
				m_validDay[ (int) DayOfWeek.Thursday ] = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="T:DateTimeReoccurringWeekly"/> is friday.
		/// </summary>
		/// <value><c>true</c> if friday; otherwise, <c>false</c>.</value>
		public bool Friday
		{
			get
			{
				return m_validDay[ (int) DayOfWeek.Friday ];
			}

			set
			{
				m_validDay[ (int) DayOfWeek.Friday ] = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="T:DateTimeReoccurringWeekly"/> is saturday.
		/// </summary>
		/// <value><c>true</c> if saturday; otherwise, <c>false</c>.</value>
		public bool Saturday
		{
			get
			{
				return m_validDay[ (int) DayOfWeek.Saturday ];
			}

			set
			{
				m_validDay[ (int) DayOfWeek.Saturday ] = value;
			}
		}

		
		/// <summary>
		/// Get the date and time of the occurrance of the pattern from the
		/// given date time.
		/// </summary>
		/// <param name="fromDate">The date to calculate the pattern from.</param>
		/// <returns>
		/// A <see cref="DateTime"/> of the next occurance.
		/// </returns>
		public override DateTime GetNextOccurrence( DateTime fromDate )
		{
			DateTime baseDate = StartDate;
			DateTime nextDate = fromDate.Date + RunTime.TimeOfDay;

			// Make sure that we have at least one day of the week set.
			if( IfNoValidDays() )
			{
				throw new ArgumentException( "You must set at lest one day of the week." );
			}

			// If the fromDate is before the start date then use the start 
			// state as the staring point.
			if( fromDate < baseDate )
			{
				return GetNextOccurrence( baseDate );
			}

			TimeSpan daysDiff = fromDate - baseDate;
			int weeks = daysDiff.Days / 7;
			int modWeeks = weeks % m_numberOfWeeks;
			DayOfWeek fromDayOfWeek = fromDate.DayOfWeek;

			if( modWeeks == 0 ) 
			{
				// This is a week that we can run in.

				// Is today a valid day
				if( m_validDay[ (int) fromDayOfWeek ] )
				{
					nextDate = fromDate.Date + RunTime.TimeOfDay;

					// If the date is today but the run time has passed then move the day out. 
					if( nextDate < fromDate )
					{
						// Get the next day that is valid this week.
						int nextDay = GetNextValidDayOfWeekExclude( fromDayOfWeek );
						if( nextDay != -1 )
						{
							nextDate = nextDate.AddDays( nextDay - (int) fromDayOfWeek );
						}
						else
						{
							// No more days valid in this week so we need to move to 
							// the next occurence.
							nextDate = GetNextDay( fromDate, m_numberOfWeeks );
						}
					}
				}
				else
				{
					// Today is not valid so get the next day in the week that is
					int nextDay = GetNextValidDayOfWeek( fromDayOfWeek );
					if( nextDay == -1 )
					{
						// No more days this week so get the next occurence, subtract one
						// from the number of weeks to account for this week.
						nextDate = GetNextDay( fromDate, m_numberOfWeeks );
					}
					else
					{
						// Advance the date to the next day this week.
						nextDate = fromDate.AddDays( nextDay - (int) fromDayOfWeek ).Date + RunTime.TimeOfDay;
					}
				}
			}
			else
			{
				// We can't run in this week to move to the next occuraence week.
				nextDate = GetNextDay( fromDate, m_numberOfWeeks - modWeeks );
			}

			return nextDate;
		}

		/// <summary>
		/// Get the text description for the occurrence.
		/// </summary>
		/// <returns></returns>
		public override string GetDisplayText()
		{
			StringBuilder msg = new StringBuilder();
			
			if( m_numberOfWeeks == 1 )
			{
				msg.Append( "Every " );
			}
			else
			{
				msg.AppendFormat( "Every {0} weeks on ", m_numberOfWeeks );
			}

			string[] dayNames = Enum.GetNames( typeof( DayOfWeek ) );
			for( int x = 0; x < 7; x++ )
			{
				if( m_validDay[ x ] )
				{
					msg.Append( dayNames[ x ] ).Append( ", " );
				}
			}

			msg.Append( " at " ).Append( RunTime.ToShortTimeString() )
				.Append( ", stating on " ).Append( StartDate.ToShortDateString() );

			return msg.ToString();
		}

		/// <summary>
		/// If there are no valid days.
		/// </summary>
		/// <returns></returns>
		private bool IfNoValidDays()
		{
			foreach( bool value in m_validDay )
			{
				if( value )
				{
					return false;
				}
			}

			return true;
		}


		/// <summary>
		/// Gets the next valid day of week.
		/// </summary>
		/// <param name="day">The day.</param>
		/// <returns></returns>
		private int GetNextValidDayOfWeek( DayOfWeek day )
		{
			int nextDay = -1;

			for( int x = 6; x >= (int) day; x-- )
			{
				if( m_validDay[ x ] )
				{
					nextDay = x;
				}
			}

			return nextDay;
		}

		/// <summary>
		/// Gets the next valid day of week exclude to day.
		/// </summary>
		/// <param name="day">The day.</param>
		/// <returns></returns>
		private int GetNextValidDayOfWeekExclude( DayOfWeek day )
		{
			int nextDay = (int) day;
			nextDay++;

			if( nextDay > 6 )
			{
				return -1;
			}

			return GetNextValidDayOfWeek( (DayOfWeek) nextDay );
		}


		/// <summary>
		/// Gets the next day.
		/// </summary>
		/// <param name="fromDate">From date.</param>
		/// <param name="weekOffset">The week offset.</param>
		/// <returns></returns>
		private DateTime GetNextDay( DateTime fromDate, int weekOffset )
		{
			// Get the day of the week.
			DayOfWeek fromDayOfWeek = fromDate.DayOfWeek;

			// Get the first day of the week that is valid
			int firstDay = GetNextValidDayOfWeek( DayOfWeek.Sunday );

			// Move forward to the begining of this week and add the number of
			// days ( m_numberOfWeeks * 7 ) and the first day of the week that
			// is valid.
			DateTime nextDate = fromDate.AddDays( 7 - (int) fromDayOfWeek ).Date;
			nextDate = nextDate.AddDays( ( weekOffset - 1 ) * 7 );
			nextDate = nextDate.AddDays( firstDay );

			return nextDate + RunTime.TimeOfDay;
		}
	}
}
